package com.hisense.ovcloud.asop.service;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.hisense.ovcloud.asop.clients.BasicDataClient;
import com.hisense.ovcloud.asop.config.Constants;
import com.hisense.ovcloud.asop.dao.CustomerDeviceDao;
import com.hisense.ovcloud.asop.dto.clients.CustomerDto;
import com.hisense.ovcloud.asop.dto.db.AllPairApplogExtDto;
import com.hisense.ovcloud.asop.dto.db.BindDevicesInfoDto;
import com.hisense.ovcloud.asop.dto.req.PairIdReq;
import com.hisense.ovcloud.asop.dto.req.PublicInfoReq;
import com.hisense.ovcloud.asop.dto.req.QueryJulinkLogsReq;
import com.hisense.ovcloud.asop.dto.resp.ASOPBaseResp;
import com.hisense.ovcloud.asop.entity.ch.AllPairApplog;
import com.hisense.ovcloud.asop.entity.ch.JulinkAnalysisLog;
import com.hisense.ovcloud.asop.entity.ch.K8sApplog;
import com.hisense.ovcloud.asop.entity.ch.WifiStatus;
import com.hisense.ovcloud.asop.tools.DateUtils;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
@Slf4j
public class PairlogService {

    public final PublicInfoService publicInfoService;

    private final BasicDataClient basicDataClient;

    private final CustomerDeviceDao customerDeviceDao;

    private final ClickhouseQueryService clickhouseQueryService;

    public PairlogService(BasicDataClient basicDataClient, ClickhouseQueryService clickhouseQueryService, CustomerDeviceDao customerDeviceDao, PublicInfoService publicInfoService) {
        this.basicDataClient = basicDataClient;
        this.clickhouseQueryService = clickhouseQueryService;
        this.customerDeviceDao = customerDeviceDao;
        this.publicInfoService = publicInfoService;
    }

    public Mono<ASOPBaseResp> fetchPairResult(ServerRequest req) {
        return basicDataClient.verifyToken(req)
                .zipWhen(checkTokenResp -> {
                    if (checkTokenResp.isExist()) {
                        try {
                            return req.bodyToMono(PublicInfoReq.class).flatMap(r -> {
                                if (StringUtils.isNotEmpty(r.getEmail())) {
                                    return basicDataClient.queryCustomerInfo(r.getEmail())
                                            .zipWhen(cl -> {
                                                if (cl.isEmpty()) {
                                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                                } else {
                                                    return Mono.just(cl.stream().map(CustomerDto::getCustomerId).toList())
                                                            .zipWhen(customerIdList -> {
                                                                if (!customerIdList.isEmpty()) {
                                                                    String customerIdListStr = "";
                                                                    if (customerIdList.size() == 1) {
                                                                        customerIdListStr = "'" + customerIdList.getFirst() + "'";
                                                                    } else {
                                                                        customerIdListStr = "'" + String.join("','", customerIdList) + "'";
                                                                    }
                                                                    String wifiId = customerDeviceDao.findWifiIdByPuidOrDeviceId(r);
                                                                    return groupAndSortThenPickOne(
                                                                            customerIdListStr,
                                                                            DateUtils.getQueryStartDateStr(r.getStartTime()),
                                                                            DateUtils.getQueryEndDateStr(r.getEndTime()),
                                                                            wifiId,
                                                                            r.getDeviceId()
                                                                    ).zipWhen(allPairlogList -> Mono.just(allPairlogList.stream().map(this::parseAllPairApplog).toList()), (x, y) -> y);
                                                                } else {
                                                                    return Mono.just(new ArrayList<>());
                                                                }
                                                            }, (x, y) -> y)
                                                            .zipWhen(result -> {
                                                                if (!result.isEmpty()) {
                                                                    return Mono.just(ASOPBaseResp.builder().data(Lists.partition(result, r.getPageSize()).get(r.getPageNum() - 1)).pageNum(r.getPageNum()).pageSize(r.getPageSize()).total(result.size()).build());
                                                                } else {
                                                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                                                }
                                                            }, (x, y) -> y)
                                                            ;
                                                }
                                            }, (x, y) -> y);
                                } else if (StringUtils.isNotEmpty(r.getDeviceId()) || StringUtils.isNotEmpty(r.getWifiId()) || StringUtils.isNotEmpty(r.getPuid())) {
                                    String wifiId = customerDeviceDao.findWifiIdByPuidOrDeviceId(r);
                                    log.info("findWifiIdByPuidOrDeviceId -> {}", wifiId);
                                    return groupAndSortThenPickOne(
                                            "",
                                            DateUtils.getQueryStartDateStr(r.getStartTime()),
                                            DateUtils.getQueryEndDateStr(r.getEndTime()),
                                            wifiId,
                                            r.getDeviceId()
                                    ).zipWhen(allPairlogList -> Mono.just(allPairlogList.stream().map(this::parseAllPairApplog).toList()), (x, y) -> y)
                                            .zipWhen(result -> {
                                                if (!result.isEmpty()) {
                                                    return Mono.just(ASOPBaseResp.builder().data(Lists.partition(result, r.getPageSize()).get(r.getPageNum() - 1)).pageNum(r.getPageNum()).pageSize(r.getPageSize()).total(result.size()).build());
                                                } else {
                                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                                }
                                            }, (x, y) -> y);
                                } else {
                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                }
                            });
                        } catch (Exception e) {
                            log.error("fetchPairResult error: {}", e.getMessage());
                            return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).resultCode(Constants.FAILED).errorMsg(e.getMessage()).build());
                        }
                    } else {
                        return Mono.just(ASOPBaseResp.builder()
                                .data(new ArrayList<>())
                                .resultCode(Constants.INVALID_TOKEN)
                                .errorMsg(Constants.ERRORS.BAD_TOKEN)
                                .build());
                    }
                }, (x, y) -> y);
    }

    public Mono<ASOPBaseResp> queryJulinkLogs(ServerRequest req) {
        return basicDataClient.verifyToken(req)
                .zipWhen(checkTokenResp -> {
                    if (checkTokenResp.isExist()) {
                        try {
                            return req.bodyToMono(QueryJulinkLogsReq.class).flatMap(r -> {
                                if (StringUtils.isNotEmpty(r.getWifiId()) && r.getLogTime() > 0) {
                                    return this.fetchAcjulinkLogs(r.getWifiId(), r.getLogTime())
                                            .zipWhen(julinklogs -> Mono.just(ASOPBaseResp.builder().data(julinklogs).build()), (x, y) -> y);
                                } else {
                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                }
                            });
                        } catch (Exception e) {
                            log.error("queryJulinkLogs error: {}", e.getMessage());
                            return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).resultCode(Constants.FAILED).errorMsg(e.getMessage()).build());
                        }
                    } else {
                        return Mono.just(ASOPBaseResp.builder()
                                .data(new ArrayList<>())
                                .resultCode(Constants.INVALID_TOKEN)
                                .errorMsg(Constants.ERRORS.BAD_TOKEN)
                                .build());
                    }
                }, (x, y) -> y);
    }

    public Mono<ASOPBaseResp> fetchWifistatusByWifiId(ServerRequest req) {
        return basicDataClient.verifyToken(req)
                .zipWhen(checkTokenResp -> {
                    if (checkTokenResp.isExist()) {
                        try {
                            return req.bodyToMono(PublicInfoReq.class).flatMap(r -> {
                                if (StringUtils.isNotEmpty(r.getEmail())) {
                                    return basicDataClient.queryCustomerInfo(r.getEmail())
                                            .zipWhen(cl -> {
                                                if (cl.isEmpty()) {
                                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                                } else {
                                                    return Mono.just(cl.stream().map(CustomerDto::getCustomerId).toList())
                                                            .zipWhen(customerIdList -> Mono.just(customerDeviceDao.fetchAllBindDevicesInfo(customerIdList, r)
                                                                            .stream().map(BindDevicesInfoDto::getWifiid).filter(StringUtils::isNotEmpty).toList()),
                                                                    (x, y) -> y)
                                                            .zipWhen(wifiIdList -> {
                                                                String wifiIdListStr = "";
                                                                if (wifiIdList.size() == 1) {
                                                                    wifiIdListStr = "'" + wifiIdList.getFirst() + "'";
                                                                } else {
                                                                    wifiIdListStr = "'" + String.join("','", wifiIdList) + "'";
                                                                }

                                                                return findWifistatusByWifiId(
                                                                        wifiIdListStr,
                                                                        DateUtils.getQueryStartDateStr(r.getStartTime()),
                                                                        DateUtils.getQueryEndDateStr(r.getEndTime())
                                                                );
                                                            }, (x, y) -> y)
                                                            .zipWhen(wifiStatusList -> {
                                                                if (!wifiStatusList.isEmpty()) {
                                                                    return Mono.just(ASOPBaseResp.builder()
                                                                            .data(Lists.partition(wifiStatusList, r.getPageSize()).get(r.getPageNum() - 1)).pageNum(r.getPageNum()).pageSize(r.getPageSize())
                                                                            .total(wifiStatusList.size()).build());
                                                                } else {
                                                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                                                }
                                                            }, (x, y) -> y);
                                                }
                                            }, (x, y) -> y);
                                } else if (StringUtils.isNotEmpty(r.getWifiId()) || StringUtils.isNotEmpty(r.getDeviceId()) || StringUtils.isEmpty(r.getPuid())) {
                                    return findWifistatusByWifiId(
                                            "'" + customerDeviceDao.findWifiIdByPuidOrDeviceId(r) + "'",
                                            DateUtils.getQueryStartDateStr(r.getStartTime()),
                                            DateUtils.getQueryEndDateStr(r.getEndTime())
                                    ).zipWhen(wifiStatusList -> {
                                        if (!wifiStatusList.isEmpty()) {
                                            return Mono.just(ASOPBaseResp.builder()
                                                    .data(Lists.partition(wifiStatusList, r.getPageSize()).get(r.getPageNum() - 1)).pageNum(r.getPageNum()).pageSize(r.getPageSize())
                                                    .total(wifiStatusList.size()).build());
                                        } else {
                                            return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                        }
                                    }, (x, y) -> y);
                                } else {
                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                }
                            });
                        } catch (Exception e) {
                            log.error("fetchWifistatusByWifiId error: {}", e.getMessage());
                            return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).resultCode(Constants.FAILED).errorMsg(e.getMessage()).build());
                        }
                    } else {
                        return Mono.just(ASOPBaseResp.builder()
                                .data(new ArrayList<>())
                                .resultCode(Constants.INVALID_TOKEN)
                                .errorMsg(Constants.ERRORS.BAD_TOKEN)
                                .build());
                    }
                }, (x, y) -> y);
    }


    public Mono<ASOPBaseResp> fetchCDCAccountInfo(ServerRequest req) {
        return basicDataClient.verifyToken(req)
                .zipWhen(checkTokenResp -> {
                    if (checkTokenResp.isExist()) {
                        try {
                            return req.bodyToMono(PublicInfoReq.class).flatMap(r -> {
                                if (StringUtils.isNotEmpty(r.getEmail())) {
                                    return selectByCdcTypeAndTime(
                                            r.getEmail(),
                                            DateUtils.getQueryStartDateStr(r.getStartTime()),
                                            DateUtils.getQueryEndDateStr(r.getEndTime())
                                    ).zipWhen(allPairApplogList -> Mono.just(allPairApplogList.stream().map(this::parseAllPairApplogCdcSpecial).toList()), (x, y) -> y)
                                            .zipWhen(result -> {
                                                if (!result.isEmpty()) {
                                                    return Mono.just(ASOPBaseResp.builder()
                                                            .data(result.size() > r.getPageNum() ? Lists.partition(result, r.getPageSize()).get(r.getPageNum() - 1) : result)
                                                            .pageNum(r.getPageNum())
                                                            .pageSize(r.getPageSize())
                                                            .total(result.size()).build());
                                                } else {
                                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                                }
                                            }, (x, y) -> y);
                                } else {
                                    return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).build());
                                }
                            });
                        } catch (Exception e) {
                            log.error("fetchCDCAccountInfo error: {}", e.getMessage());
                            return Mono.just(ASOPBaseResp.builder().data(new ArrayList<>()).resultCode(Constants.FAILED).errorMsg(e.getMessage()).build());
                        }
                    } else {
                        return Mono.just(ASOPBaseResp.builder()
                                .data(new ArrayList<>())
                                .resultCode(Constants.INVALID_TOKEN)
                                .errorMsg(Constants.ERRORS.BAD_TOKEN)
                                .build());
                    }
                }, (x, y) -> y);
    }

    private AllPairApplog parseAllPairApplog(AllPairApplog allPairApplog) {
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(allPairApplog.getType()) && allPairApplog.getType().equals(Constants.PairLogType.ADD_DEVICE_TRACK)) {
            String[] pairTypes = allPairApplog.getProperties().replaceAll(", pairType: ", "##").replaceAll(", wifiId", "##").split("##");
            if (pairTypes.length > 1) {
                allPairApplog.setPairtype(pairTypes[1]);
            }
        }
        return allPairApplog;
    }

    private AllPairApplogExtDto parseAllPairApplogCdcSpecial(AllPairApplog allPairApplog) {
        AllPairApplogExtDto t = new AllPairApplogExtDto();
        BeanUtils.copyProperties(allPairApplog, t);
        return disposeAllPairApplogExtDtoProperties(t);
    }

    private AllPairApplogExtDto disposeAllPairApplogExtDtoProperties(AllPairApplogExtDto t) {
        String properties = t.getProperties();
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(properties) && properties.contains(",")) {
            Arrays.stream(properties
                            .replaceAll(", ", "#")
                            .replaceAll(",", "#").split("#"))
                    .filter(x -> StrUtil.containsIgnoreCase(x, "errorMessage\"") ||
                            StrUtil.containsIgnoreCase(x, "\"page") ||
                            StrUtil.containsIgnoreCase(x, "\"path\"") ||
                            StrUtil.containsIgnoreCase(x, "\"errorCode\""))
                    .forEach(p -> {
                        String property = p
                                .replaceAll("\"", "")
                                .replaceAll(" ", "")
                                .replaceAll("\\{", "")
                                .replaceAll("}", "");
                        if (StringUtils.isNotEmpty(property) && property.contains(":")) {
                            switch (property.split(":")[0].toLowerCase()) {
                                case "errormessage" -> {
                                    t.setErrorMessage(property.split(":")[1]);
                                }
                                case "page" -> {
                                    t.setPage(property.split(":")[1]);
                                }
                                case "path" -> {
                                    t.setPath(property.split(":")[1]);
                                }
                                case "errorcode" -> {
                                    t.setErrorCode(Integer.parseInt(property.split(":")[1]));
                                }
                            }
                        }
                    });
        }

        return t;
    }

    public Mono<ASOPBaseResp> fetchPairDetail(ServerRequest req) {
        return basicDataClient.verifyToken(req)
                .zipWhen(checkTokenResp -> {
                    if (checkTokenResp.isExist()) {
                        return req.bodyToMono(PairIdReq.class).flatMap(r -> findPairAppLogByPairId(
                                r.getPairid(),
                                DateUtils.getQueryStartDateStr(r.getStartTime()),
                                DateUtils.getQueryEndDateStr(r.getEndTime()))
                                .zipWhen(result -> Mono.just(ASOPBaseResp.builder()
                                        .data(Lists.partition(result, r.getPageSize()).get(r.getPageNum() - 1))
                                        .pageNum(r.getPageNum())
                                        .pageSize(r.getPageSize())
                                        .total(result.size()).build()), (x, y) -> y));
                    } else {
                        return Mono.just(ASOPBaseResp.builder()
                                .data(new ArrayList<>())
                                .resultCode(Constants.INVALID_TOKEN)
                                .errorMsg(Constants.ERRORS.BAD_TOKEN)
                                .build());
                    }
                }, (x, y) -> y);
    }

    private Mono<List<AllPairApplog>> groupAndSortThenPickOne(String userIds, String beginTime, String endTime, String wifiId, String deviceId) {
        AtomicReference<List<AllPairApplog>> topNItems = new AtomicReference<>(new ArrayList<>());
        return clickhouseQueryService.filterPairAppLogAndGroupByPairId(Constants.PairLogType.ADD_DEVICE_TRACK,
                        userIds, beginTime, endTime, wifiId, deviceId)
                .zipWhen(records -> {
                    if (!records.isEmpty()) {
                        records.stream().collect(Collectors.groupingBy(AllPairApplog::getPairid)).forEach((key, group) -> {
                            List<AllPairApplog> sortedGroup = group.stream()
                                    .sorted(Comparator.comparing(AllPairApplog::getLog_id).reversed())
                                    .limit(Constants.TOP_N_SIZE)
                                    .toList();
                            topNItems.get().addAll(sortedGroup);
                        });
                    }
                    return Mono.just(topNItems.get());
                }, (x, y) -> y);
    }

    private Mono<List<AllPairApplog>> selectByCdcTypeAndTime(String mail, String beginTime, String endTime) {
        return clickhouseQueryService.selectByCdcTypeAndTime(Constants.PairLogType.CDC_TYPE,
                mail, beginTime, endTime);
    }

    private Mono<List<AllPairApplog>> findPairAppLogByPairId(String pairid, String beginTime, String endTime) {
        return clickhouseQueryService.findPairAppLogByPairId(pairid, Constants.PairLogType.ADD_DEVICE_TRACK, beginTime, endTime)
                .flatMap(records -> Mono.just(records.stream().map(this::parseAllPairApplog).toList()));
    }

    private Mono<List<WifiStatus>> findWifistatusByWifiId(String wifiId, String beginTime, String endTime) {
        return clickhouseQueryService.findWifistatusByWifiId(wifiId,
                beginTime, endTime);
    }

    public Mono<List<K8sApplog>> fetchAcjulinkLogs(String wifiId, long logTime) {
        Mono<List<JulinkAnalysisLog>> listMono = clickhouseQueryService.queryJulinkLogs(wifiId, logTime);
        if (listMono == null){
            return Mono.just(new ArrayList<>());
        }
        return listMono.map(logs -> logs.stream().map(this::convertToK8sApplog).toList());
    }

    private K8sApplog convertToK8sApplog(JulinkAnalysisLog log){
        return K8sApplog.builder()
                .logtime(log.getLogtime())
                .pod_name(log.getPodName())
                .message(log.getChannelType() + ", " + log.getChannelNo() + ", "+log.getDuration() +"ms, " + log.getError())
                .build();
    }


    private long calculateTimestampForPair(String pairTime, String zoneId) {
        if (StringUtils.isNotEmpty(zoneId) && zoneId.contains("/")) {
            return LocalDateTime.parse(pairTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).atZone(ZoneId.of(zoneId)).toEpochSecond() * 1000;
        } else {
            return LocalDateTime.parse(pairTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).atZone(ZoneId.of("UTC")).toEpochSecond() * 1000;
        }
    }
}
